package common;

import java.io.File;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.struts.action.ActionForm;
import org.junit.After;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;

import servletunit.struts.MockStrutsTestCase;

import com.ibatis.sqlmap.client.SqlMapClient;

/**
 * JUnitテストの基礎クラス。<BR>
 * 
 * @author SEH119
 */
@Transactional
@TransactionConfiguration
@WebAppConfiguration
@ContextConfiguration(locations = { "classpath:/resources/applicationContext.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
public class BaseTestCase extends MockStrutsTestCase {

    public static SimpleDateFormat DEFUAL_FORMAT = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

    AbstractTestDataFileReader dataFileReader;

    @Autowired
    WebApplicationContext wac;

    @Autowired
    SqlMapClient sqlMapClient;

    @Before
    @Override
    public void setUp() throws Exception {
        super.setUp();
        setConfigFile("/resources/struts-config.xml");
        setContextDirectory(new File("WebContent"));
        getActionServlet().getServletContext().setAttribute(
                WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac);
        dataFileReader = new TestDataFileReader(getClass().getSimpleName(), null, getClass());
    }

    @After
    @Override
    public void tearDown() throws Exception {
        super.tearDown();
    }

    /**
     * テストケース実行のDBデータ準備。<BR>
     * ファイルからSQLを読み込んで、DB更新SQLを実行し、データを準備する。<BR>
     * 
     * @throws SQLException
     */
    public void initDbData() throws SQLException {
        if (dataFileReader.getFileName() != null && dataFileReader.getMethodName() != null) {
            executeUpdate(dataFileReader.getSqls());
        }
    }

    /**
     * VERIFY_STRUTS.xlsを使って、Actionテスト実行の準備をする。<BR>
     */
    public void initActionTest() {
        // Session情報設定
        Map<String, Object> sessionMap = dataFileReader.getBeforeSession();
        if (sessionMap != null) {
            for (String key : sessionMap.keySet()) {
                addSession(key, sessionMap.get(key));
            }
        }
        // Request情報設定
        RequestInfo requestInfo = dataFileReader.getRequestInfo();
        if (requestInfo != null) {
            Map<String, String> param = requestInfo.getParam();
            Map<String, String[]> params = requestInfo.getParams();
            for (String key : param.keySet()) {
                addRequestParameter(key, param.get(key));
            }
            for (String key : params.keySet()) {
                addRequestParameter(key, params.get(key));
            }
            // PathInfo設定
            setRequestPathInfo(requestInfo.getPathInfo());
        }
    }

    public void verifyAction() {
        // Session情報検証 TODO
        Map<String, Object> sessionMap = dataFileReader.getAfterSession();
        // Struts情報検証
        StrutsInfo strutsInfo = dataFileReader.getStrutsInfo();
        verifyStrutsInfo(strutsInfo);
        // ActionForm情報検証 TODO
        ActionForm actionForm = dataFileReader.getActionForm();
    }

    private void verifyStrutsInfo(StrutsInfo strutsInfo) {
        if (strutsInfo.getForward() != null) {
            verifyForward(strutsInfo.getForward());
        }
        if (strutsInfo.getForwardPath() != null) {
            verifyForwardPath(strutsInfo.getForwardPath());
        }
        if (strutsInfo.getActionErrorMessage() == null
                || strutsInfo.getActionErrorMessage().length == 0) {
            verifyNoActionErrors();
        } else {
            verifyActionErrors(strutsInfo.getActionErrorMessage());
        }
        if (strutsInfo.getActionMessage() == null
                || strutsInfo.getActionMessage().length == 0) {
            verifyNoActionMessages();
        } else {
            verifyActionMessages(strutsInfo.getActionMessage());
        }
        if (strutsInfo.getInputForward() != null) {
            verifyInputForward();
        }
        if (strutsInfo.getInputTilesForward() != null) {
            verifyInputTilesForward(strutsInfo.getInputTilesForward());
        }
    }

    /**
     * configファイルのdataSourceを通して、java.sql.Statementを作る。<BR>
     * 
     * @return java.sql.Statement
     * @throws SQLException
     */
    public Statement getStatement() throws SQLException {
        return sqlMapClient.getDataSource().getConnection().createStatement();
    }

    /**
     * DB更新SQLを実行する<BR>
     * 
     * @param sqls
     * @return DB影響行数
     * @throws SQLException
     */
    public int executeUpdate(String... sqls) throws SQLException {
        Statement statement = getStatement();
        int rls = 0;
        for (String sql : sqls) {
            rls += statement.executeUpdate(sql);
        }
        statement.close();
        return rls;
    }

    /**
     * DB更新SQLを実行する<BR>
     * 
     * @param sqls
     * @return DB影響行数
     * @throws SQLException
     */
    public int executeUpdate(List<String> sqls) throws SQLException {
        Statement statement = getStatement();
        int rls = 0;
        for (String sql : sqls) {
            rls += statement.executeUpdate(sql);
        }
        statement.close();
        return rls;
    }

    /**
     * ファイルからQUERY SQLを読み込んで実行する結果とファイルから読み込んだ期待結果を検証する。<BR>
     * 
     * @param sheetName
     * @throws SQLException
     */
    public void verifyDbData(String sheetName) throws SQLException {
        String sql = dataFileReader.getQuerySql(sheetName);
        Result result = dataFileReader.getResult(sheetName);
        // DB検証
        List<List<Map<String, Object>>> results = new ArrayList<List<Map<String, Object>>>();
        for (Row row : result.getRows()) {
            // 行
            List<Map<String, Object>> rowTp = new ArrayList<Map<String, Object>>();
            // カラム
            Map<String, Object> colums = new HashMap<String, Object>();
            for (Colum colum : row.getColums()) {
                colums.put(colum.getName(), colum.getValue());
            }
            rowTp.add(colums);
            results.add(rowTp); 
        }
        verifyQueryResult(sql, results);
    }

    /**
     * QUERY SQLを実行する結果を検証する。<BR>
     * 
     * @param sql
     * @param results
     * @throws SQLException
     */
    public void verifyQueryResult(String sql, List<List<Map<String, Object>>> results) throws SQLException {
        Statement statement = getStatement();
        ResultSet rs = statement.executeQuery(sql);
        if (rs.next() && results.size() > 0) {
            for (int i = 0; i < results.size(); i++) {
                List<Map<String, Object>> row = results.get(i);
                Map<String, Object> colums = row.get(0);
                for (String key : colums.keySet()) {
                    Object value = colums.get(key);
                    if (value instanceof Date) {
                        assertEquals((Date) value, rs.getDate(key));
                    } else if (value instanceof Number){
                        assertEquals(((Number) value).doubleValue(), rs.getDouble(key));
                    } else {
                        assertEquals(value, rs.getObject(key));
                    }
                }
                boolean hasNext = rs.next();
                if (!hasNext && i + 1 < results.size()) {
                    fail("検索結果と期待結果の行数が相違しているので、テスト失敗しました。");
                }
            }
        } else {
            fail("検索結果がない、あるいは期待結果がないので、テスト失敗しました。");
        }
        rs.close();
        statement.close();
    }

    /**
     * Sessionに値をセットする。<BR>
     * 
     * @param name
     * @param value
     */
    public void addSession(String name, Object value) {
        getSession().setAttribute(name, value);
    }

    /**
     * Sessionから値を削除する。<BR>
     * 
     * @param name
     */
    public void removeSession(String name) {
        getSession().removeAttribute(name);
    }

    /**
     * Sessionから値を取得する。<BR>
     * 
     * @param name
     * @return
     */
    public Object getSession(String name) {
        return getSession().getAttribute(name);
    }

    /**
     * 検証ファイル名を取得する(default:simple class name.)。<BR>
     * 
     * @return String
     */
    public String getFileName() {
        return dataFileReader.getFileName();
    }

    /**
     * 検証ファイルを設定する。<BR>
     * 
     * @param fileName
     */
    public void setFileName(String fileName) {
        dataFileReader.setFileName(fileName);
    }

    public String getMethodName() {
        return dataFileReader.getMethodName();
    }

    public void setMethodName(String methodName) {
        dataFileReader.setMethodName(methodName);
    }

    public AbstractTestDataFileReader getDataFileReader() {
        return dataFileReader;
    }

    public void setDataFileReader(AbstractTestDataFileReader dataFileReader) {
        this.dataFileReader = dataFileReader;
    }

}
